#!/bin/bash
################################################################################
#   Id:     $Id: $
#   File:   csync
#   Author: John Griffin
#   Date:   September 9, 2009
#   Description:
#       Push an application out to another server
#
#   Example:
#       cpush /www/var/application
#
#  TODO:
#       - implement extension
#       - implement copy off
#       - implement .async config file
################################################################################
#
# globals
DEBUG=on
USAGE="${0##*/} [-p] [-s server:path] [-d server:path] [-e extension]
 -p pull code 
 -s path to take source code from
 -d path to write source code to
 -e extension to put on copied directory (not yet implemented)
 -c copy off (not yet implemented)
"
source_server=""                    # server to pull files from
source_path=""                      # path to source files
destination_server=""               # server to sync files to
destination_path=""                 # path to sync files to
pull_code="n"                       # are we pulling or pushing files
extension=`date +'%Y%m%d%H%M%S'`    # extension to put on the backed up directory

function writeDebug {
    # $1 - message
    if [ "$DEBUG" != "" ]; then
        echo "$1"
    fi
}

function copyDirectory {
    # $1 - source directory
    # $2 - destination directory
    
    writeDebug "copyDirectory $1 $2"
    pushd $2 > /dev/null
    (cd $1; tar cf - * 2> /dev/null) | tar xf -
    popd > /dev/null
}


function localSyncDirectory {
    # $1 - source directory
    # $2 - destination directory
    
    # check to see if there is .async file present in the source directory
    include=""
    exclude=""
    if [ -f "$1/.async" ]; then
        tmp=`grep ^[+-]"$1/.async"`
        for pattern in ${tmp##+}; do
            include=$include" --include=$pattern"
        done
        tmp=`grep ^EXCLUDE: "$1/.async"`
        for pattern in ${tmp##-}; do
            exclude=$exclude" --exclude=$pattern"
        done
    fi
    rsync -ar --delete $include $exclude "$1/" "$2"
}


function remoteSyncDirectory {
    # $1 - source server
    # $2 - source directory
    # $3 - destination directory
    
    writeDebug "remoteSyncDirectory $1 $2 $3"
    # check to see if there is .async file present in the source directory
    include=""
    exclude=""
    if [ -f "$1/.async" ]; then
        tmp=`grep ^[+-]"$1/.async"`
        for pattern in ${tmp##+}; do
            include=$include" --include=$pattern"
        done
        tmp=`grep ^EXCLUDE: "$1/.async"`
        for pattern in ${tmp##-}; do
            exclude=$exclude" --exclude=$pattern"
        done
    fi
    rsync -ar --delete $include $exclude "${1}:${2}/" "${3}"
}


function updateLink {
    # $1 - symbolic link
    # $2 - target path
    # $3 - extension
    # check to see if the target exists and is a directory
    if [ -h "$1" ]; then
        rm "$1"
        ln -s "$2" "$1"
    else
        mv "$1" "${1}_safe.${3}"
        ln -s "$2" "$1"
    fi
}


function updateDirectory {
    # $1 - old directory
    # $2 - new directory
    # $3 - extension
    mv "$1" "${1}.${3}"
    mv "$2" "$1"
}


function localCopy {
    # $1 - source path
    # $2 - destination path
    # $3 - extension
    writeDebug "localCopy $1 $2 $3"
    
    source_path=`cd $1; echo $PWD`
    destination_path=`cd $2; echo $PWD`
    writeDebug "source_path=$source_path"
    writeDebug "destination_path=$destination_path"
    
    # check to see if the target exists and is a directory
    if [ -h "$destination_path" ]; then
        actual_path=`readlink -fn $destination_path`
    else
        actual_path="$destination_path"
    fi
    writeDebug "actual_path=$actual_path"

    # only do the sync if we are dealing with a directory
    if [ -d "$actual_path" ]; then
        # create the target path
        target_path="${actual_path}.${$}"
        mkdir -p "$target_path"
    
        copyDirectory "$actual_path" "$target_path"

        syncDirectory "$actual_path" "$source_server" "$source_path"
    
        # if we are dealing with a link update the link
        if [ -h "$destination_path" ]; then
            updateLink "$actual_path" "$target_path" "${3}"
        else
            updateDirectory "$actual_path" "$target_path" "${3}"
        fi
    fi
}

function remoteCopy {
    # $1 - source server
    # $2 - source path
    # $3 - destination path
    # $4 - extension
    writeDebug "remoteCopy $1 $2 $3"
    
    source_path="$2"
    destination_path=`cd "$3"; echo $PWD`
    writeDebug "source_path=$source_path"
    writeDebug "destination_path=$destination_path"
    
    # check to see if the target exists and is a directory
    if [ -h "$destination_path" ]; then
        actual_path=`readlink -fn $destination_path`
    else
        actual_path="$destination_path"
    fi
    writeDebug "actual_path=$actual_path"

    # only do the sync if we are dealing with a directory
    if [ -d "$actual_path" ]; then
        # create the target path
        target_path="${actual_path}.${$}"
        mkdir -p "$target_path"
    
        copyDirectory "$actual_path" "$target_path"
        remoteSyncDirectory "$1" "$source_path" "$target_path"
    
        # if we are dealing with a link update the link
        if [ -h "$destination_path" ]; then
            updateLink "$actual_path" "$target_path" "${4}"
        else
            updateDirectory "$actual_path" "$target_path" "${4}"
        fi
    fi
}

function localToRemoteCopy {
    # $1 - source path
    # $2 - destination server
    # $3 - destination path
    
    writeDebug "localToRemoteCopy $1 $2 $3"
    hostname=`hostname`
#    ssh ${2} "async -p -s ${hostname}:${1} -d ${3}"
    ssh ${2} "/home/john/Desktop/async -p -s ${hostname}:${1} -d ${3}"
}


function remoteToRemoteCopy {
    # $1 - source server
    # $2 - source path
    # $3 - destination server
    # $4 - destination path
    ssh ${1} "async -s ${2} -d ${3}:${4}"
}


################################################################################
## MAINLINE
################################################################################
# parse args
while getopts d:s:p OPTION
do
    case $OPTION in
        d) if [ "${OPTARG/*:*/}" == "" ]; then
                destination_server=${OPTARG%%:*}
                destination_path=${OPTARG##*:}
           else
                destination_server=""
                destination_path=${OPTARG}
           fi ;;
        s) if [ "${OPTARG/*:*/}" == "" ]; then
                source_server=${OPTARG%%:*}
                source_path=${OPTARG##*:}
           else
                source_server=""
                source_path=${OPTARG}
           fi;;
        p) pull_code="y";;
        *) echo "$USAGE"
           exit 1
    esac
done

# make sure source and destination paths are set
source_path=${source_path:-$PWD}
destination_path=${destination_path:-$PWD}

writeDebug "destination_server=$destination_server"
writeDebug "destination_path=$destination_path"
writeDebug "source_server=$source_server"
writeDebug "source_path=$source_path"

# if the source and destination servers are the same then the source and 
# destination paths must be different
if [ "$source_server" == "$destination_server" ]; then
    if [ "$source_path" == "$destination_path" ]; then
        echo "Error: source and destination must be different"
        exit 1
    fi
fi

# are we pushing files or pulling files?
if [ "$pull_code" == "n" ]; then
    # are we moving code on this server?
    hostname=`hostname`
    if [ "$source_server" == "" -o "$source_server" == "$hostname" ]; then
        if [ "$destination_server" == "" -o "$destination_server" == "$hostname" ]; then
            localCopy "$source_path" "$destination_path" "$extension"
        else
            localToRemoteCopy "$source_path" "$destination_server" "$destination_path"
        fi
    else
        remoteToRemoteCopy "$source_server" "$source_path" "$destination_server" "$destination_path"
    fi
else
    # we are pulling files
    writeDebug "Pulling files"
    remoteCopy "$source_server" "$source_path" "$destination_path" "$extension"
fi
